<!-- END HEADER --> class: title-slide, center, middle #.title[Introduzione a R] #.subtitle[Giornata 3 - Programmazione in R] <img src="data:image/png;base64,#img/arca_logo.svg" width="10%" style="display: block; margin: auto;" /> ###.location[Corsi ARCA - @DPSS] ###.author[Filippo Gambarota] --- # Programmazion in R Quello che vedremo in questa sezione sono i principali **costrutti della programmazione** e la loro applicazione in R. Ci sono alcuni punti da considerare: - Sono concetti trasversali estremamente utili - Sono alla base di qualcunque **funzionalità già implementata in R** - Vi permettono di fare qualunque cosa con il linguaggio --- # Programmazion in R - Disclaimer Ci sono delle cose che per tempo e complessità non possiamo affrontare e che sono R specifiche. Per questi aspetti avanzati del linguaggio, il libro [**Advanced R**](https://adv-r.hadley.nz/) è la cosa migliore ```r put_image("adv_R.png") ``` <img src="data:image/png;base64,#img/adv_R.png" style="display: block; margin: auto;" /> --- class: section, center, middle # Costrutti della programmazione in R --- # Costrutti della programmazione in R - Funzioni - Programmazione condizionale - Programmazione iterativa --- # Funzioni Analogalmente alle *funzioni matematiche* la funzione in programmazione consiste nell' **astrarre** una serie di operazioni (nel nostro caso una porzione di codice) definendo una serie di operazioni che forniti degli *input* forniscono degli *output* eseguendo una serie di *operazioni* --- # Funzioni Prendiamo l'equazione di una retta: `\(y = 2x + 3\)` dove `\(3\)` ```r x <- 1:10 y <- 2*x + 3 plot(x, y, xlim = c(0, 10), ylim = c(0, 30), type = "l") ``` <img src="data:image/png;base64,#4_programmazione_files/figure-html/unnamed-chunk-3-1.png" width="2100" style="display: block; margin: auto;" /> --- # Funzioni Se vogliamo *astrarre* questa operazione in modo da renderla più generale e utile dobbiamo definire: - **argomenti funzione**: quelle che in matematica sono le *variabili* - **corpo funzione**: le **operazioni** che la funzione deve eseguire usando gli argomenti - **output funzione**: cosa la funzione deve **restituire** come risultato --- # Funzioni - Argomenti Gli **argomenti** sono quelle parti variabili della funzione che vengono definiti e poi sono necessari ad eseguire la funzione stessa. Se vogliamo *astrarre* la retta che abbiamo visto prima dobbiamo definire alcune parti come **variabili**: La retta `\(y = mx + q\)` dove `\(m\)` è la pendenza, `\(x\)` sono i valori della variabile `\(x\)` e `\(q\)` è l'intercetta (valore di `\(y\)` quando `\(x = 0\)`). Quindi possiamo definire in R: ```r retta <- function(m, x, q){ # argomenti # body # output } ``` --- # Funzioni - Body Il corpo della funzione sono le operazioni da eseguire utilizzando gli argomenti in input. Nel caso della retta semplicemente moltiplicare `\(m\)` per ogni valore di `\(x\)` e aggiungere `\(q\)`. In questo modo otteniamo tutti i valori di `\(y\)`: ```r retta <- function(m, x, q){ # argomenti y <- m*x + q # output } ``` --- # Funzioni - Output L'output è il **risultato che la funzione ci restituisce** dopo aver eseguito tutte le operazioni. Nel nostro caso della retta, vogliamo ottenere il rispettivo valore di `\(y\)` per ogni valore di `\(x\)` inserito: ```r retta <- function(m, x, q){ # argomenti y <- m*x + q return(y) # restituisce y } ``` --- # Funzioni - Risultato finale .pull-left[ ```r retta <- function(m, x, q){ # argomenti y <- m*x + q return(y) # restituisce y } x <- 1:10 m <- 0.3 q <- 0 y <- retta(m, x, q) ``` ] .pull-right[ <img src="data:image/png;base64,#4_programmazione_files/figure-html/unnamed-chunk-8-1.png" width="2100" style="display: block; margin: auto;" /> ] --- class: section, center, middle # Programmazione condizionale --- # Programmazione condizionale In programmazione solitamente è necessario non solo eseguire una serie di operazione **MA** eseguire delle operazione in funzione di alcune **condizioni** Facciamo un esempio pratico, la funzione `summary()` in R fornisce un risultato diverso in base al tipo di input. Come è possibile tutto questo? Tramite l'utilizzo di **condizioni**: ```r x <- 1:10 # vettore numerico y <- factor(rep(c("a", "b", "c"), each = 10)) # vettore di stringhe summary(x) ``` ``` ## Min. 1st Qu. Median Mean 3rd Qu. Max. ## 1.00 3.25 5.50 5.50 7.75 10.00 ``` ```r summary(y) ``` ``` ## a b c ## 10 10 10 ``` --- # Programmazione condizionale Anche se non sappiamo quali operazioni svolga la funzione `summary()` possiamo immaginare una cosa simile ```r summary <- function(argomento){ # se l'argomento è un vettore numerico # esegui --> operazioni a,b,c # se l'argomento è un vettore stringa # esegui --> operazioni d,e,f # ... } ``` --- # Programmazione condizionale Il concetto di `se <condizione> allora fai <operazione>` si traduce in programmazione tramite quelli che si chiamano `if statement`: ```r put_image("if_chart.png") ``` <img src="data:image/png;base64,#img/if_chart.png" style="display: block; margin: auto;" /> --- # Programmazione condizionale Per lavorare con gli `if statements` dobbiamo avere chiaro: - il concetto di *operatori logici* ovvero `TRUE` e `FALSE` - il concetto di *operazioni logiche* `TRUE and TRUE = TRUE` --- # Programmazione condizionale Quando una sola condizione non basta... ```r put_image("ifelse_chart.png") ``` <img src="data:image/png;base64,#img/ifelse_chart.png" style="display: block; margin: auto;" /> --- # Programmazione condizionale Per poter capire quale struttura condizionale utilizzare è importante capire bene il problema che dobbiamo risolvere. Ritornando all'esempio della funzione `summary()`, immaginiamo di avere 2 tipi di dati in R; stringhe e numeri. In questo caso è sufficiente avere un `if statement` che controlla se l'elemento è una stringa/numero e per tutto il resto applicare l'opposto. --- # Programmazione condizionale - Tip Esiste una famiglia di funzioni con prefisso `is.*` che fornisce `TRUE` quando la tipologia di oggetto corrisponde a quella richiesta e `FALSE` in caso contrario. ```r x <- 1:10 is.numeric(x) ``` ``` ## [1] TRUE ``` ```r is.factor(x) ``` ``` ## [1] FALSE ``` ```r is.character(x) ``` ``` ## [1] FALSE ``` Possiamo usare queste funzioni per creare un flusso condizionale nella nostra funzione `summary()` --- # Programmazione condizionale Scriviamo una funzione che restituisca la `media` quando il vettore è numerico e la tabella di frequenza (con la funzione `table()`) ```r my_summary <- function(x){ # testiamo la condizione if(is.numeric(x)){ return(mean(x)) }else{ return(table(x)) } } x <- 1:10 my_summary(x) ``` ``` ## [1] 5.5 ``` ```r x <- rep(c("a","b","c"), c(10, 2, 8)) my_summary(x) ``` ``` ## x ## a b c ## 10 2 8 ``` --- class: section, center, middle # Programmazione iterativa --- # Programmazione iterativa Il concetto di *iterazione* è alla base di qualsiasi operazione nei linguaggi di programmazione. In R molte delle operazioni sono vettorizzate. Questo rende il linguaggio più efficiente e pulito MA nasconde il concetto di *iterazione* --- # Programmazione iterativa Esempio: se io vi chiedo di usare la funzione `print()` per scrivere `"hello world"` nella console 10 volte, come fate? ```r msg <- "Hello World" print(msg) ``` ``` ## [1] "Hello World" ``` ```r print(msg) ``` ``` ## [1] "Hello World" ``` ```r print(msg) ``` ``` ## [1] "Hello World" ``` ```r print(msg) ``` ``` ## [1] "Hello World" ``` ```r print(msg) ``` ``` ## [1] "Hello World" ``` ```r print(msg) ``` ``` ## [1] "Hello World" ``` ```r print(msg) ``` ``` ## [1] "Hello World" ``` ```r print(msg) ``` ``` ## [1] "Hello World" ``` ```r print(msg) ``` ``` ## [1] "Hello World" ``` ```r print(msg) ``` ``` ## [1] "Hello World" ``` # Programmazione iterativa Quello che ci manca è un modo di ripetere una certa operazione, senza effettivamente ripetere il codice manualmente. Ci sono vari costrutti che ci permettono di ripetere operazioni: - Cicli `for` - Cicli `while` - `*apply` family - altri --- # For ```r put_image("for_loop.png") ``` <img src="data:image/png;base64,#img/for_loop.png" style="display: block; margin: auto;" /> --- # For La scrittura di un ciclo `for` è: ```r for(i in 1:n){ # operazioni } ``` --- # Scomponiamo il ciclo for Ci sono diversi elementi: - `for(){}`: è l'implementazione in R (in modo simile all'`if statement`) - `i`: questo viene chiamato *iteratore* o *indice*. E' un indice generico che può assumere qualsiasi valore e nome. Per convenzione viene chiamato `i`, `j` etc. Questo tiene conto del numero di iterazioni che il nostro ciclo deve fare - `in <valori>`: questo indica i valori che assumerà l'*iteratore* all'interno del ciclo - `{ # operazioni }`: sono le operazioni che i ciclo deve eseguire --- # Hello world come ciclo for ```r # vogliamo che il ciclo ripeta l'operazione 10 volte for(i in 1:10){ print("hello world") } ``` ``` ## [1] "hello world" ## [1] "hello world" ## [1] "hello world" ## [1] "hello world" ## [1] "hello world" ## [1] "hello world" ## [1] "hello world" ## [1] "hello world" ## [1] "hello world" ## [1] "hello world" ``` --- # Ma l'iteratore? La potenza del ciclo `for` sta nel fatto che l'iteratore `i` assume i valori del vettore specificato dopo `in`, uno alla volta: ```r for(i in 1:10){ print(i) } ``` ``` ## [1] 1 ## [1] 2 ## [1] 3 ## [1] 4 ## [1] 5 ## [1] 6 ## [1] 7 ## [1] 8 ## [1] 9 ## [1] 10 ``` --- # For con iteratore vs senza Questa è una distinzione importante quanto sottile, notate la differenza tra questi due cicli: .pull-left[ ```r vec <- 1:5 for(i in 1:length(vec)){ print(vec[i]) } ``` ``` ## [1] 1 ## [1] 2 ## [1] 3 ## [1] 4 ## [1] 5 ``` ] .pull-right[ ```r vec <- 1:5 for(i in vec){ print(i) } ``` ``` ## [1] 1 ## [1] 2 ## [1] 3 ## [1] 4 ## [1] 5 ``` ] --- # While Il ciclo `while` è una versione più generale del ciclo for. Per funzionare utilizza una *condizione logica* e non un iteratore e un range di valori come nel `for`. ```r while(condizione){ # operazioni } ``` Dove il ciclo continuearà fino a che la `condizione` è vera --- # While - (Fun) Provate a scrivere questo ciclo `while` e vedere cosa succede: ```r x <- 10 while (x < 15) { print(x) } ``` Chi mi sa spiegare il risultato? --- # While Questo esercizio è utile per capire che il `while` è un ciclo non pre-determinato e quindi necessita sempre di un modo per essere interrotto, facendo diventare la condizione falsa. ```r x <- 5 while (x < 15) { print(x) x <- x + 1 } ``` ``` ## [1] 5 ## [1] 6 ## [1] 7 ## [1] 8 ## [1] 9 ## [1] 10 ## [1] 11 ## [1] 12 ## [1] 13 ## [1] 14 ``` --- # Applicazioni dei cicli Gli esempi finora sono semplici ma poco utili. Quando il queste strutture iterative sono veramente utili? Molte delle funzioni che utilizziamo come ad esempio `sum()`, `mean()`, etc. hanno al loro interno una sturttura iterativa Immaginiamo di non avere la funzione `sum()` e di volerla ricreare, come facciamo? Idee? --- # Somma come iterazione Scomponiamo concettualmente la somma, sommiamo i numeri da 1 a 10: - prendo il primo e lo sommo al secondo (`somma = 1 + 2`) - prendo la `somma` e la sommo al 3 elemento `somma = somma + 3` - ... In pratica abbiamo: - il nostro vettore da sommare - un oggetto `somma` che accumula progressivamente le somme precedenti --- # Somma come iterazione ```r somma <- 0 # inizializziamo la somma a 0 x <- 1:10 for(i in seq_along(x)){ somma <- somma + x[i] } ``` --- # Somma come iterazione Mettiamo tutto dentro una funzione ```r my_sum <- function(x){ somma <- 0 # inizializziamo la somma a 0 for(i in seq_along(x)){ somma <- somma + x[i] } return(somma) } x <- rnorm(100) my_sum(x) ``` ``` ## [1] 2.236982 ``` ```r sum(x) ``` ``` ## [1] 2.236982 ``` --- class: section, center, middle # Ma in R c'è qualcosa di meglio... --- # Ma in R c'è qualcosa di meglio... In R, l'utilizzo **esplicito** dei cicli `for` non è molto diffuso, per 2 motivi: - R è un linguaggio fortemente **funzionale** - R è un linguaggio spesso **vettorizzato** - I cicli `for` sono molto verbosi e non sempre leggibili - I cicli `for` in R, se non scritti bene, possono essere *estremamente lenti* --- class: section, center, middle # `*apply` family --- # `*apply` family Immaginate di avere una `lista` di vettori, e di voler applicare la stessa funzione/i ad ogni elemento della lista. Come fare? ^[1] - applico manualmente la funzione selezionando gli elementi - ciclo `for` che itera sugli elementi della lista e applica la funzione/i - ... ```r my_list <- list( vec1 <- rnorm(100), vec2 <- runif(100), vec3 <- rnorm(100), vec4 <- rnorm(100) ) ``` .footnote[ Hadley Wickam - The joy of functional programming - [link](https://www.youtube.com/watch?v=bzUmK0Y07ck&t=1453s) ] --- # `*apply` family Applichiamo `media`, `mediana` e `deviazione standard`: .pull-left[ ```r means <- vector(mode = "numeric", length = length(my_list)) medians <- vector(mode = "numeric", length = length(my_list)) stds <- vector(mode = "numeric", length = length(my_list)) for(i in 1:length(my_list)){ means[i] <- mean(my_list[[i]]) medians[i] <- median(my_list[[i]]) stds[i] <- sd(my_list[[i]]) } ``` ] .pull-right[ ```r means ``` ``` ## [1] 0.04100252 0.46996826 -0.16812770 -0.04192122 ``` ```r medians ``` ``` ## [1] 0.12469061 0.46666825 -0.10911857 -0.05624449 ``` ```r stds ``` ``` ## [1] 0.9945286 0.2846280 1.0096402 0.8258954 ``` ] --- # `*apply` family Funziona tutto! ma: - il `for` è molto laborioso da scrivere gli indici sia per la lista che per il vettore che stiamo popolando - dobbiamo *pre-allocare delle variabili* (per il motivo della velocità che dicevo) - 8 righe di codice (per questo esempio semplice) --- # `*apply` family In R è presente una famiglia di funzioni `*apply` come `lapply`, `sapply`, etc. che permettono di ottenere lo stesso risultato in modo più conciso, rapido e semplice: ```r means <- sapply(my_list, mean) medians <- sapply(my_list, median) stds <- sapply(my_list, sd) means ``` ``` ## [1] 0.04100252 0.46996826 -0.16812770 -0.04192122 ``` ```r medians ``` ``` ## [1] 0.12469061 0.46666825 -0.10911857 -0.05624449 ``` ```r stds ``` ``` ## [1] 0.9945286 0.2846280 1.0096402 0.8258954 ``` --- # `*apply` family - Bonus Prima di introdurre l'`*apply` family un piccolo bonus. Sfruttando il fatto che in R **tutto è un oggetto** possiamo scrivere in modo ancora più conciso: ```r my_funs <- list(median = median, mean = mean, sd = sd) lapply(my_list, function(vec) sapply(my_funs, function(fun) fun(vec))) ``` ``` ## [[1]] ## median mean sd ## 0.12469061 0.04100252 0.99452865 ## ## [[2]] ## median mean sd ## 0.4666683 0.4699683 0.2846280 ## ## [[3]] ## median mean sd ## -0.1091186 -0.1681277 1.0096402 ## ## [[4]] ## median mean sd ## -0.05624449 -0.04192122 0.82589538 ``` Amazing! ora cerchiamo di dare un senso a queste righe di codice! --- # `*apply` family ```r *apply(<lista>, <funzione>) ``` - cosa può essere la `lista`? - lista - dataframe - vettore - cosa può essere la `funzione`? - funzione *base* o importata *pacchetto* - funzione *custom* - funzione *anonima* --- # `*apply` family - intuizione Prima di analizzare l'`*apply` family, credo sia utile un ulteriore parallelismo con il ciclo `for` che abbiamo visto. `*apply` non è altro che un ciclo `for`, leggermente semplificato: .pull-left[ ```r vec <- 1:5 for(i in vec){ print(i) } ``` ``` ## [1] 1 ## [1] 2 ## [1] 3 ## [1] 4 ## [1] 5 ``` ] .pull-right[ ```r vec <- 1:5 res <- sapply(vec, print) ``` ``` ## [1] 1 ## [1] 2 ## [1] 3 ## [1] 4 ## [1] 5 ``` ] --- # `*apply` family - spoiler funzione anonima Quindi come il ciclo `for` scritto come `i in vec` assegna al valore `i` un elemento per volta dell'oggetto `vec`, internamente le funzioni `*apply` prendono il primo elemento dell'oggetto in input (`lista`) e applicano direttamente la funzione che abbiamo scelto. C'è un modo per rendere esplicito questo, anche nelle funzioni `*apply`: .pull-left[ ```r vec <- 1:5 res <- sapply(vec, print) ``` ``` ## [1] 1 ## [1] 2 ## [1] 3 ## [1] 4 ## [1] 5 ``` ] .pull-right[ ```r vec <- 1:5 res <- sapply(vec, function(i) print(i)) ``` ``` ## [1] 1 ## [1] 2 ## [1] 3 ## [1] 4 ## [1] 5 ``` ] --- # `*apply` e funzioni custom ```r center_var <- function(x){ x - mean(x) } my_list <- list( vec1 = runif(10), vec2 = runif(10), vec3 = runif(10) ) lapply(my_list, center_var) ``` ``` ## $vec1 ## [1] -0.55796773 0.11497327 0.14739926 0.02858120 0.35566639 -0.28120796 0.19898969 ## [8] -0.37595723 0.01182529 0.35769783 ## ## $vec2 ## [1] -0.3375940 -0.4225581 -0.4340135 0.3957083 0.4201151 -0.3723340 0.4015886 ## [8] 0.1535234 0.4147392 -0.2191750 ## ## $vec3 ## [1] -0.20090273 0.43776162 -0.03057997 0.06735352 -0.19086981 0.31044614 -0.29202494 ## [8] -0.18944090 0.51797483 -0.42971774 ``` --- # `*apply` e funzioni anonime Una funzione anonima è una funzione non salvata in un oggetto ma scritta per essere **eseguita direttamente**, all'interno di altre funzioni che lo permettono: ```r lapply(my_list, function(x) x - mean(x)) ``` ``` ## $vec1 ## [1] -0.55796773 0.11497327 0.14739926 0.02858120 0.35566639 -0.28120796 0.19898969 ## [8] -0.37595723 0.01182529 0.35769783 ## ## $vec2 ## [1] -0.3375940 -0.4225581 -0.4340135 0.3957083 0.4201151 -0.3723340 0.4015886 ## [8] 0.1535234 0.4147392 -0.2191750 ## ## $vec3 ## [1] -0.20090273 0.43776162 -0.03057997 0.06735352 -0.19086981 0.31044614 -0.29202494 ## [8] -0.18944090 0.51797483 -0.42971774 ``` Come per i cicli `for` (ricordo che `*apply` e `for` sono identici), `x` è solo un placeholder (analogo di `i`) e può essere qualsiasi lettera o nome --- # Tutte le tipologie di `*apply` Vediamo tutti i tipi di `*apply` che ci sono. Alcuni sono più *utili* altri più *robusti* e altri ancora poco utilizzati: - `lapply()`: la funzione di base - `sapply()`: `simplified-apply` - `tapply()`: poco utilizzata, utile con i *fattori* - `apply()`: utile per i *dataframe/matrici* - `mapply()`: versione multivariata, utilizza *più liste contemporaneamente* - `vapply()`: utilizzata dentro le funzioni e pacchetti --- # `lapply` `lapply` sta per list-apply e restituisce sempre una lista, applicando la funzione ad ogni elemento della lista in input: ```r res <- lapply(my_list, mean) res ``` ``` ## $vec1 ## [1] 0.6293721 ## ## $vec2 ## [1] 0.4618877 ## ## $vec3 ## [1] 0.4806322 ``` ```r class(res) ``` ``` ## [1] "list" ``` --- # `sapply` `sapply` sta per simplified-apply e (cerca) di restituire una versione più semplice di una lista, applicando la funzione ad ogni elemento della lista in input: ```r res <- sapply(my_list, mean) res ``` ``` ## vec1 vec2 vec3 ## 0.6293721 0.4618877 0.4806322 ``` ```r class(res) ``` ``` ## [1] "numeric" ``` --- # `apply` `apply` funziona in modo specifico per dataframe o matrici, applicando una funzione alle righe o alle colonne: - `apply(dataframe, index, fun)` ```r # index 1 = riga, 2 = colonna my_dataframe <- data.frame(my_list) head(my_dataframe) ``` ``` ## vec1 vec2 vec3 ## 1 0.07140435 0.12429370 0.2797295 ## 2 0.74434535 0.03932958 0.9183938 ## 3 0.77677134 0.02787416 0.4500523 ## 4 0.65795329 0.85759591 0.5479857 ## 5 0.98503847 0.88200279 0.2897624 ## 6 0.34816413 0.08955362 0.7910784 ``` ```r apply(my_dataframe, 1, mean) ``` ``` ## [1] 0.1584759 0.5673563 0.4182326 0.6878450 0.7189346 0.4095987 0.6268151 0.3866724 ## [9] 0.8388104 0.4268990 ``` ```r apply(my_dataframe, 2, mean) ``` ``` ## vec1 vec2 vec3 ## 0.6293721 0.4618877 0.4806322 ``` ```r apply(my_dataframe, 2, center_var) ``` ``` ## vec1 vec2 vec3 ## [1,] -0.55796773 -0.3375940 -0.20090273 ## [2,] 0.11497327 -0.4225581 0.43776162 ## [3,] 0.14739926 -0.4340135 -0.03057997 ## [4,] 0.02858120 0.3957083 0.06735352 ## [5,] 0.35566639 0.4201151 -0.19086981 ## [6,] -0.28120796 -0.3723340 0.31044614 ## [7,] 0.19898969 0.4015886 -0.29202494 ## [8,] -0.37595723 0.1535234 -0.18944090 ## [9,] 0.01182529 0.4147392 0.51797483 ## [10,] 0.35769783 -0.2191750 -0.42971774 ``` --- # `tapply` `tapply` permette di applicare una funzione ad un *vettore*, dividendo questo vettore in base ad una variabile categoriale: - `tapply(dataframe, index, fun)`: dove `index` è un vettore di stringa o un fattore ```r vec <- rnorm(75) index <- rep(c("a", "b", "c"), each = 25) tapply(vec, index, mean) ``` ``` ## a b c ## -0.30035471 0.02699657 0.08977458 ``` --- # `vapply` `vapply` è una versione più *solida* delle precedenti dal punto di vista di programmazione. In pratica permette (e richiede) di specificare in anticipo la tipologia di dato che ci aspettiamo come risultato `vapply(X = , FUN = , FUN.VALUE = ,... )` ```r vapply(my_list, FUN = mean, FUN.VALUE = numeric(length = 1)) ``` ``` ## vec1 vec2 vec3 ## 0.6293721 0.4618877 0.4806322 ``` - `my_list, FUN = mean`: è esattamente uguale a `sapply/lapply` - `FUN.VALUE = numeric(length = 1)`: indica che ogni risultato è un singolo valore numerico --- # `mapply` Questa è quella più complicata ma anche molto utile. Praticamente permette di gestire più liste contemporaneamente per scenari più complessi. Ad esempio vogliamo usare la funzione `rnorm()` e generare vettori con diverse **medie** e **deviazioni stardard** in combinazione. ```r medie <- list(10, 20, 30, 40) stds <- list(1,2,3,4) mapply(function(x, y) rnorm(n = 10, mean = x, sd = y), medie, stds, SIMPLIFY = FALSE) ``` ``` ## [[1]] ## [1] 9.375608 10.536672 9.869751 10.463658 9.916795 10.784369 9.429727 8.207395 ## [9] 9.327376 10.276685 ## ## [[2]] ## [1] 16.52207 23.59996 16.27939 19.20234 22.57155 25.18252 16.53918 21.81490 19.36781 ## [10] 18.24297 ## ## [[3]] ## [1] 34.12078 29.02602 30.81952 26.93646 30.79180 31.97780 31.33861 28.37731 30.12908 ## [10] 32.18210 ## ## [[4]] ## [1] 39.97731 35.58794 37.28414 37.90489 34.68557 45.00547 43.14427 39.10887 40.79675 ## [10] 42.34915 ``` **IMPORTANTE**, tutte le liste incluse devono avere la stessa dimensione! --- # `mapply` ```r mapply(function(x, y) rnorm(n = 10, mean = x, sd = y), medie, stds, SIMPLIFY = FALSE) ``` - `function(...)`: è una funzione anonima come abbiamo visto prima che può avere *n* elementi - `rnorm(n = 10, mean = x, sd = y)`: è l'effettiva funzione anonima dove abbiamo i placeholders `x` and `y` - `medie, stds`: sono **in ordine** le liste corrispondenti ai placeholders indicati, quindi `x = medie` e `y = stds`. - `SIMPLIFY = FALSE`: semplicemente dice di restituire una lista e non cercare (come `sapply`) di semplificare il risultato --- # `mapply` come `for` Lo stesso risultato (in modo più verboso e credo meno intuitivo) si ottiene con un `for` usando più volte l'iteratore `i`: ```r medie <- list(10, 20, 30, 40) stds <- list(1,2,3,4) res <- vector(mode = "list", length = length(medie)) for(i in 1:length(medie)){ res[[i]] <- rnorm(10, mean = medie[[i]], sd = stds[[i]]) } res ``` ``` ## [[1]] ## [1] 11.079366 9.250481 10.134097 7.787982 10.494051 10.059371 9.119569 9.165540 ## [9] 9.086043 12.514314 ## ## [[2]] ## [1] 21.44982 16.75876 21.44235 22.50518 17.75876 19.50682 21.55240 21.45746 20.58236 ## [10] 18.64282 ## ## [[3]] ## [1] 28.38066 33.12865 25.92947 35.02015 34.69184 28.87021 36.00287 29.27272 30.26059 ## [10] 29.70473 ## ## [[4]] ## [1] 33.51109 39.20242 37.62966 36.82737 35.88148 38.31363 38.01748 42.08322 42.01732 ## [10] 36.82897 ``` --- class: section, center, middle # `*apply` alcune precisazioni --- # `*apply` vettore vs lista Abbiamo sempre usato esplicitamente `liste` fino ad ora, ma le funzioni `*apply` sono direttamente applicabili anche a **vettori** - se usiamo un vettore di *n* elementi, allora itereremo da `1:n` - se usiamo una lista di *n* elementi, allora iteriamo da `1:n` dove il singolo elemento può essere qualsiasi cosa ```r my_vec <- 1:5 my_list <- list(a = 1:2, b = 3:4, c = 5:6) res <- sapply(my_vec, print) ``` ``` ## [1] 1 ## [1] 2 ## [1] 3 ## [1] 4 ## [1] 5 ``` ```r res <- sapply(my_list, print) ``` ``` ## [1] 1 2 ## [1] 3 4 ## [1] 5 6 ``` --- # `*apply` come un `for` Nulla ci vieta (ma perdiamo l'aspetto intuitivo e conciso) di usare le funzioni `*apply` esattamente come un ciclo `for`, usando un **iteratore**: ```r medie <- c(10, 20, 30, 40) stds <- c(1,2,3,4) res <- lapply(1:length(medie), function(i){ rnorm(n = 10, mean = medie[i], sd = stds[i]) }) ``` Trovo tuttavia più chiara l'alternativa usando `mapply`: ```r mapply(function(x, y) rnorm(n = 10, mean = x, sd = y), medie, stds, SIMPLIFY = FALSE) ``` --- class: section, center, middle # Extra: `purrr::map*` --- # Extra: `purrr::map*` .pull-left[ ```r put_image("purrr.svg") ``` <img src="data:image/png;base64,#img/purrr.svg" style="display: block; margin: auto;" /> ] .pull-right[ Senza addentrarci troppo in questo modo, c'è una famiglia di funzioni che una volta imparato `*apply` vi consiglio di usare perchè più consistenti e intuitive, la `map*` family. ] --- # Extra: `purrr::map*` Per usare `purrr::map*` è sufficiente installare il pacchetto `purrr` con `install.packages("purrr")` ed iniziare ad usare le nuove funzioni. La sintassi è esattamente la stessa di `*apply` (qualche modifica ma potete usare la stessa) ma invece che usare una funzione per tutto, abbiamo molte funzioni per ogni casistica: - `map(lista, funzione)` è l'analogo di `lapply()` e fornisce sempre una lista - `map_dbl(lista, funzione)` applica la funzione ad ogni elemento e **si aspetta che** il risultato sia un vettore di *double* - `map_lgl(lista, funzione)` applica la funzione ad ogni elemento e **si aspetta che** il risultato sia un vettore *logico* - `map2/pmap_*` sono rispettivamente applicare la funzione a 2/n liste (analogo di `mapply()`) --- class: section, center, middle # Extra: `replicate()` and `repeat()` --- # Extra: `replicate()` and `repeat()` Ci sono altre due funzioni in R che permettono di *iterare*. Sono meno utilizzate perchè si ottengono gli stessi risultati usando un semplice `for` o `*apply`. - `replicate()` permette di ripetere un operazione *n* volte, senza però utilizzare un `iteratore` o un `placeholder`. - `repeat()` anche repeat permette di ripetere ma fino a che non si verifica un certa condizione (**logica**). Ha una struttura simile al ciclo `while` --- class: section, center, middle # Extra: Formula syntax --- # Formula syntax In R molte operazioni vengono eseguite usando la **formula syntax** `something ~ something else` ad esempio: - modelli statistici: `lm(y ~ x, data = data)`, `t.test(y ~ factor, data = data)` - plot: `boxplot(y ~ x, data = data)` - ... In cosa consiste? --- # Formula syntax Senza andare nei dettagli tecnici, R usa una cosa che si chiama *lazy evaluation*. In altri termini "salva" delle operazioni per essere eseguite in un secondo momento. Tutti sappiamo che se scriviamo un nome (senza virgolette) e questo non è associato ad un oggetto otteniamo un errore. Tuttavia alcune funzioni come `library()` non forniscono errore. Perchè? ```r stats # errore ``` ``` ## Error in eval(expr, envir, enclos): object 'stats' not found ``` ```r library(stats) # no errore ``` --- # Formula syntax La ragione è che R è in grado di salvare un'espressione per usarla poi in uno specifico contesto (ad esempio dentro una funzione). La `formula syntax` è un esempio. Usando la tilde `~` possiamo creare delle `formule` che R può utilizzare in specifici contesti: ```r y ``` ``` ## [1] a a a a a a a a a a b b b b b b b b b b c c c c c c c c c c ## Levels: a b c ``` ```r x ``` ``` ## [1] 0.70193169 -1.28175818 -0.40171193 0.97645311 0.92567678 0.17635762 1.21550061 ## [8] -2.25702256 0.72265468 0.57934365 2.26972145 -0.50473548 -0.32209501 0.02664335 ## [15] 0.83266864 -0.60948029 -0.29348775 -0.05010149 1.07552871 -1.79348320 -1.28373659 ## [22] -0.04890344 0.22310671 -0.55442870 1.73574266 2.17420597 0.65595963 0.19647047 ## [29] 1.12722964 -0.09687218 0.95881523 0.63468376 -0.44433713 -0.22874811 0.69269046 ## [36] 2.09239499 -0.64197046 0.14678330 0.22226243 -0.54989232 -0.52823810 0.19149548 ## [43] 1.22438577 0.28973388 0.16006033 -1.27848757 -1.22075353 0.50935623 -1.85577659 ## [50] -2.14680789 -1.17354973 0.20871637 0.82398219 -0.55446080 0.29524896 0.46354714 ## [57] -1.44543139 -1.10342236 1.00078347 -0.92544899 -0.02423803 -0.18097671 -1.11716960 ## [64] -0.53132253 -1.12581736 -0.27299044 -1.03090454 -0.77374286 0.44453666 0.30637758 ## [71] 0.21825115 -1.20423952 0.29451005 -0.06877878 0.87116702 0.82955825 -0.66043580 ## [78] -0.76875825 0.01784938 0.58650104 0.15122109 -1.25108234 1.10967075 0.76755754 ## [85] -0.51733253 1.08136296 1.01128719 -1.26617808 1.05003482 -0.94108220 -0.53412364 ## [92] 0.79840961 1.90992886 0.58507202 -0.24921251 0.59355545 0.14920862 0.36654952 ## [99] -0.62742521 0.30518941 ``` ```r y ~ x ``` ``` ## y ~ x ## <environment: 0x000001764add0178> ``` ```r my_formula <- y ~ x class(my_formula) ``` ``` ## [1] "formula" ``` --- # Formula syntax e `aggregate()` Un esempio utile è la funzione `aggregate()` molto interessante per applicare funzioni a dataframe. Immaginate di avere il dataset `iris` e calcolare la media per ogni livello del fattore `Species`: ```r tapply(iris$Sepal.Length, iris$Species) ``` ``` ## [1] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 ## [44] 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 ## [87] 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 ## [130] 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 ``` ```r aggregate(Sepal.Length ~ Species, FUN = mean, data = iris) ``` ``` ## Species Sepal.Length ## 1 setosa 5.006 ## 2 versicolor 5.936 ## 3 virginica 6.588 ``` ```r # Anche creando un oggetto, ma solo come formula my_formula <- Sepal.Length ~ Species my_char <- "Sepal.Length ~ Species" aggregate(my_char, FUN = mean, data = iris) ``` ``` ## Error in aggregate.data.frame(as.data.frame(x), ...): argument "by" is missing, with no default ``` ```r # Viene lo stesso usando $ e senza specificare data = aggregate(iris$Sepal.Length, iris$Species, FUN = mean) ``` ``` ## Error in aggregate.data.frame(as.data.frame(x), ...): 'by' must be a list ``` --- # Formula syntax e `aggregate()` Ma anche operazioni più complesse: ```r my_iris <- iris my_iris$fac <- rep(c("a", "b", "c"), 50) aggregate(Sepal.Length ~ Species + fac, mean, data = my_iris) ``` ``` ## Species fac Sepal.Length ## 1 setosa a 5.052941 ## 2 versicolor a 5.770588 ## 3 virginica a 6.756250 ## 4 setosa b 5.011765 ## 5 versicolor b 6.018750 ## 6 virginica b 6.447059 ## 7 setosa c 4.950000 ## 8 versicolor c 6.023529 ## 9 virginica c 6.570588 ``` --- # Replicate .pull-left[ `replicate(n, expr)` - `n` è il numero di ripetizioni - `expr` è la porzione di codice da ripetere ```r # Campioniamo 1000 volte da una normale e facciamo la media AKA distribuzione campionaria della media nrep <- 1000 nsample <- 30 media <- 100 ds <- 30 means <- replicate(n = nrep, expr = { mean(rnorm(nsample, media, ds)) }) ``` ] .pull-right[ <img src="data:image/png;base64,#4_programmazione_files/figure-html/unnamed-chunk-56-1.png" width="2100" style="display: block; margin: auto;" /> ] --- # `repeat()` ```r repeat { # cose da ripetere if(...){ # condizione da valutare break # ferma il loop } } ``` ```r i <- 1 repeat { print(i) i = i + 1 if(i > 3){ break } } ``` ``` ## [1] 1 ## [1] 2 ## [1] 3 ``` --- # `repeat()` vs `while` <!-- TODO revise repeat vs loop --> .pull-left[ ```r i <- 1 repeat { print(i) i = i + 1 if(i > 3){ break } } ``` ``` ## [1] 1 ## [1] 2 ## [1] 3 ``` ] .pull-right[ ```r i <- 1 while(i < 4){ print(i) i <- i + 1 } ``` ``` ## [1] 1 ## [1] 2 ## [1] 3 ``` ] - `repeat` valuta la condizione una volta finita l'iterazione, mentre `while` all'inizio. Se la condizione non è `TRUE` all'inizio, il `while` non parte mentre `repeat` si.